package SSIHoSEChannel;
use strict;
use Encode;
use vars qw /@ISA/;
use XML::Twig;
use Data::Dumper;
use Text::Trim;
use StockInfoSet;
use StockInfo;
use MarketInfo;
use ChannelInfo; 
use DataCollectionChannel; 
@ISA = ("DataCollectionChannel");

sub new{
	my $class = shift;
	my $self = {};
	$self->{URL} = 'http://ssi.com.vn/HoSE/StandardData/HoSE/SSI_SG_PRICE_ONLINE_TABLE.XML';
	$self->{STOCK_FIELD_FORMAT} = [ 'Description', 'Symbol', 'ChangedPrice', 'RefPrice', 'SS1MatchPrice', 'SS1MatchVolume', 'SS2MatchPrice', 'SS2MatchVolume', 
									'TempPrice', 'TempVolume', 'CeilingPrice', 'FloorPrice', 'FRoom', 
									'Best3BidPrice', 'Best3BidVolume', 'Best2BidPrice', 'Best2BidVolume', 'Best1BidPrice', 'Best1BidVolume',
									'Best1OffPrice', 'Best1OffVolume', 'Best2OffPrice', 'Best2OffVolume', 'Best3OffPrice', 'Best3OffVolume',
									'HighestPrice', 'LowestPrice', 'FBuy' ];
									
	$self->{MARKET_FIELD_FORMAT} = [ 'Index', 'ChangeIndex', 'TotalVolume', 'TotalValue', 'Advances', 
									 'NoChange', 'Declines', 'TradingDate', 'Time', 'SessionNo', 'NP_V', 'NV_V', 'PrevIndex', 'Transactions', 'ChangePercent' ];

	$self->{SECURITY_CENTER_CODE} = 'HoSE';	
	bless $self, $class;
	return $self;
}

##################################
##	get html page of given url	##
##################################
sub get_data_source{
	my $self = shift;
		my $browser = LWP::UserAgent->new(
	  	agent => 'Mozilla/5.0 (compatible; MSIE 4.01; Windows95)',
	  	timeout =>	$self->{TIMEOUT} 	
	 );
	$browser->parse_head(0);	  
 	my $resp = $browser->get($self->{URL});
 	return ($resp->content, $resp->is_success, $resp->status_line);   	
}

##################################
## get stock info set			##
## input:						##
## output:						##
##		a StockInfoSet object	##
##################################
sub get_stock_info_set{
	my $self = shift;
	my $content;
	my $is_success;
	my $status_line;
	my $proc_time;
	my $channel_name = 	$self->{NAME};
	my $start_time = time();
	my $stock_info_set = StockInfoSet::new();
	
	# assign sys sequence, start_time and name to channelinfo object
	$stock_info_set->{CHANNEL_INFO}->set('SYS_SEQ', shift);
	$stock_info_set->{CHANNEL_INFO}->set('NAME', $channel_name);
	$stock_info_set->{CHANNEL_INFO}->set('START_TIME', $start_time);
	# assign priority
	$stock_info_set->{MARKET_INFO}->set('PRIORITY', $self->{PRIORITY});
	
	
	# get data source from url 
	while(time < $start_time + $self->{TIMEOUT}){ # check time out
		$self->{STATUS}->{CODE} = 'CONNECTING';
		($content, $is_success, $status_line) = $self->get_data_source();	
		if (!$is_success){
			$self->{STATUS}->{CODE} = 'RECONNECTING';
			$self->{STATUS}->{MSG} = "Can't fetch data source($status_line)";
			next;	 
		}else{
			$self->{STATUS}->{CODE} = 'OK';
			$self->{STATUS}->{MSG} = "Data source is fetched($status_line)";
			last;			
		}
	}
	if ($self->{STATUS}->{CODE} ne 'OK'){
		$self->{STATUS}->{CODE} = 'TIMEOUT';
		$self->{STATUS}->{MSG} = 'Can\'t fetch data source(connection timeout)';
		# caculate processing time 
		$proc_time = time - $start_time;
		$stock_info_set->{CHANNEL_INFO}->set('PROCESSING_TIME', $proc_time);
		$stock_info_set->{CHANNEL_INFO}->set('STATUS', $self->{STATUS}->{CODE});
		$stock_info_set->{CHANNEL_INFO}->set('MESSAGE', $self->{STATUS}->{MSG});
		$stock_info_set->{MARKET_INFO} = undef;
		$stock_info_set->{STOCK_INFO_SET} = undef;
		return ($stock_info_set,$self->{STATUS});			
	}
	
	#my $proc_time = time - $start_time;
	#print "\n Getting time: $proc_time \n";
	
	# get the market stock_info chunk and market_info chunk from the data source
	$content =~ /(<\?.*\?>).*(<STOCK_INFO>.*<\/STOCK_INFO>).*(<MARKET_INFO>.*<\/MARKET_INFO>)/;
	my $xml_tag = $1;
	my $content_stock_info = $xml_tag . $2; 
	my $content_market_info = $xml_tag . $3;
	# refine stock info content (replace &lt; by < and &gt; by > and nbsp; by ' ') 
	$content_stock_info =~ s/\&lt\;/</g;
	$content_stock_info =~ s/\&gt\;/>/g;
	$content_stock_info =~ s/\&amp\;//g;
	$content_stock_info =~ s/nbsp\;//g;
	$content_stock_info =~ s/\&nbsp\;//g;
	$content_stock_info =~ s/<br>/ /g;
	
	#refine market info content
	$content_market_info =~ s/\&lt\;/</g;
	$content_market_info =~ s/\&gt\;/>/g;
	$content_market_info =~ s/\&amp\;//g;
	$content_market_info =~ s/nbsp\;//g;
	$content_market_info =~ s/\&nbsp\;//g;
	$content_market_info =~ s/<br>/ /g;
	
	#parse the market_info chunk
	my $parser = XML::Twig->new();
	$parser->parse(Encode::encode_utf8($content_market_info));
	my $root = $parser->root();
	my $market_info_format = $self->{MARKET_FIELD_FORMAT};
	my $changed_percent = '0';
	foreach my $field (@$market_info_format){
		my $field_value = trim(Encode::decode_utf8($root->first_child_text(uc($field))));
		# Remove comma from the digital format
		if($field_value =~ /[\d|,]*/){
			$field_value =~ s/,//g;
		}
		# Volume unit is 10 then TotalVolume *= 10 
		if($field eq 'TotalVolume' ) { $field_value *= 10; };
		if($field eq 'ChangeIndex'){
				$field_value =~ /(.*)\((.*)\%\)/;
				$field_value = $1;
				$changed_percent = $2;			
		}
		$stock_info_set->{MARKET_INFO}->set($field, $field_value);				
	}	
	# free the parser
	$parser->purge();
	
	if(!ref($stock_info_set->{MARKET_INFO}->get('ChangePercent'))){
		$stock_info_set->{MARKET_INFO}->set('ChangePercent', $changed_percent); 
	}
	if( $changed_percent ne '0' ){
		$stock_info_set->{MARKET_INFO}->set('PrevIndex',sprintf('%0.2f', 100*($stock_info_set->{MARKET_INFO}->get('ChangeIndex') / $changed_percent )));
	}else{
		$stock_info_set->{MARKET_INFO}->set('PrevIndex', 0);
	}
	# set market time based on the delay time
	if($self->{DELAY_TIME}){
		my $market_time = DateCalc(ParseDate("today"), "- " . $self->{DELAY_TIME} . " seconds");			
		$stock_info_set->{MARKET_INFO}->set('Time', UnixDate($market_time, "%H:%M:%S"));						
	}else{
		$stock_info_set->{MARKET_INFO}->set('Time', undef);			
	}
	# set channel_name and sys_seq for market_info
	$stock_info_set->{MARKET_INFO}->set('CHANNEL_NAME', $stock_info_set->{CHANNEL_INFO}->get('NAME'));
	$stock_info_set->{MARKET_INFO}->set('SYS_SEQ', $stock_info_set->{CHANNEL_INFO}->get('SYS_SEQ'));
	$stock_info_set	->{MARKET_INFO}->set('SECURITY_CENTER', $self->{SECURITY_CENTER_CODE});
	
	#parse the stock_info chunk
	$parser = XML::Twig->new();
	$parser->parse(Encode::encode_utf8($content_stock_info));
	$root = $parser->root();
	my $stock_set = $stock_info_set->{STOCK_INFO_SET};
	foreach my $child_node ($root->children('STOCK')){ # process each <STOCK> node
		my $stock_info = StockInfo::new("ObjectInfo");
		my @children = $child_node->children(); 
		for(my $i=0; $i< @{$self->{STOCK_FIELD_FORMAT}} ; $i++){ 
			# Fields that aren't contained in the data source will be set to undef
			my $field_value = (ref($children[$i]))?trim($children[$i]->text()):undef;
			my $field = $self->{STOCK_FIELD_FORMAT}->[$i];
			# Fields that are contained in the data source but don't have their values will be set to '0'
			if(ref($children[$i]) && $field_value eq ''){$field_value = '0';}
			# Remove indicator character(FL, CE) from ChangedPrice field
			if(@{$self->{STOCK_FIELD_FORMAT}}[$i] eq 'ChangedPrice'){$field_value =~ s/[a-zA-Z]//g;};
			if(($self->{STOCK_FIELD_FORMAT}->[$i] =~ /Best.*Price/) || ($self->{STOCK_FIELD_FORMAT}->[$i] eq '')){
				$field_value = '0';	 
			};
			if($field =~ /.*Price/ ){
					$field_value = -1 if($field_value eq 'ATO');
					$field_value = -2 if($field_value eq 'ATC');
			}
			$stock_info->set(@{$self->{STOCK_FIELD_FORMAT}}[$i], $field_value);		 	 
		}
		
		$stock_info->set('CHANNEL_NAME', $stock_info_set->{CHANNEL_INFO}->get('NAME'));
		$stock_info->set('SYS_SEQ', $stock_info_set->{CHANNEL_INFO}->get('SYS_SEQ'));	
		push @$stock_set, $stock_info;
	}
	# free the parser
	$parser->purge();
	
	if(ref($stock_info_set->{MARKET_INFO}) && ref($stock_info_set->{STOCK_INFO_SET})){
		$self->{STATUS}->{CODE} = 'SUCCEEDED';
		$self->{STATUS}->{MSG} = 'The data source is pasrsed sucessfully and put into a stock_info_set object';		
	}else{
		$self->{STATUS}->{CODE} = 'FAILED';
		$self->{STATUS}->{MSG} = 'The data source isn\'t pasrsed sucessfully';		
	}	
	# caculate processing time 
	$proc_time = time - $start_time;
	$stock_info_set->{CHANNEL_INFO}->set('PROCESSING_TIME', $proc_time);
	$stock_info_set->{CHANNEL_INFO}->set('STATUS', $self->{STATUS}->{CODE});
	$stock_info_set->{CHANNEL_INFO}->set('MESSAGE', $self->{STATUS}->{MSG});	
	return ($stock_info_set,$self->{STATUS});	
}
##################################
## get market time				##
## input:						##
## output:						##
##		{HOUR => $hour 			##
##		 MIN => $time }			##
##################################
sub get_market_time{
	my $self = shift;
	my $content;
	my $is_success;
	my $status_line;
	my $start_time = time();
	my $channel_name = $self->{NAME};
	
	# get data source from url 
	($content, $is_success, $status_line) = $self->get_data_source();
	
	if (!$is_success){
		$self->{STATUS}->{CODE} = 'TIMEOUT';
		$self->{STATUS}->{MSG} = 'Can\'t fetch data source(connection timeout)';
		return (undef,$self->{STATUS});			
	}else{
		$self->{STATUS}->{CODE} = 'OK';
		$self->{STATUS}->{MSG} = "Data source is fetched($status_line)";					
	}
	
	# get the market stock_info chunk and market_info chunk from the data source
	$content =~ /(<\?.*\?>).*<TIME>(.*)<\/TIME>/;
	if($2){
		my ($hour,$minute) = split(":",$2);
		$self->{STATUS}->{CODE} = 'OK';
		$self->{STATUS}->{MSG} = "Time is fetched| Channel $channel_name: market openned";
		# update channel real time
		$self->{REAL_TIME}->{HOUR} = $hour;
		$self->{REAL_TIME}->{MIN} = $minute;
		return({HOUR => $hour, MIN => $minute}, $self->{STATUS});		
	}else{
		$self->{STATUS}->{CODE} = 'IVLFORMAT';
		$self->{STATUS}->{MSG} = "Can't get the market time| Channel $channel_name: data in a invalid format";	
		return (undef, $self->{STATUS});	
	}
}
#######################
## get market status ##
#######################
sub get_market_status{
	my $self = shift;
	my $market_start_time = shift;
	my $market_stop_time = shift;
	my $market_time;
	my $market_status = {
		CODE => "CLOSING",
		MSG => ""
	};
		
	# if the center is HoSE, check real time
	my ($market_time_tmp, $status) = $self->get_market_time();
	if(!ref($market_time_tmp)){
		return $status;
	}
	my $hour = $market_time_tmp->{HOUR};
	my $min = $market_time_tmp->{MIN};
	$market_time = ParseDate($hour . ':' . $min . ':00');			
	$market_status->{MSG} = 'Market time: ' . $market_time_tmp->{HOUR} . ':' . $market_time_tmp->{MIN} ;
	if(Date_Cmp($market_time, $market_start_time) >= 0 && Date_Cmp($market_time, $market_stop_time) <= 0){
		$market_status->{CODE} = 'OPENING';
	}else{
		$market_status->{CODE} = 'CLOSING';
	}
	return $market_status;
}


1;
